package cn.cj.erp.common.excel.excel;

import cn.cj.erp.common.enums.BaseEnum;
import cn.cj.erp.common.excel.anno.Excel;
import cn.cj.erp.common.excel.enums.ColumnType;
import cn.cj.erp.common.excel.enums.ExcelType;
import cn.cj.erp.common.excel.format.ExcelFormatter;
import cn.cj.erp.common.exception.ServiceException;
import cn.cj.erp.common.utils.Assert;
import cn.cj.erp.common.utils.Convert;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.enums.IEnum;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.util.*;

/**
 * Excel导入处理
 *
 * @author jianchen
 * @version 1.0.0
 * @date 2024/8/7
 */
@Slf4j
public class ExcelImportHandler {
    /**
     * 对excel表单指定表格索引名转换成list
     *
     * @param sheetName 表格索引名
     * @param titleNum  标题占用行数
     * @param is        输入流
     * @return 转换后集合
     */
    public static <T> List<T> handle(Class<T> tClass,
                                     String sheetName,
                                     InputStream is,
                                     int titleNum) throws Exception {
        ExcelBean<T> handlerBean = new ExcelBean<>(tClass);
        ExcelBeanInitializer.getInstance(handlerBean).importInit(is, sheetName);
        Sheet sheet = handlerBean.getSheet();
        Map<String, PictureData> pictures = PoiUtil.getSheetImages(handlerBean.getWb(), sheet);
        // 获取最后一个非空行的行下标，比如总行数为n，则返回的为n-1
        int rows = sheet.getLastRowNum();
        if (rows <= 0) {
            return Lists.newArrayList();
        }
        List<T> list = Lists.newArrayList();
        Map<Integer, Object[]> fieldsMap = createCellMap(sheet, handlerBean, titleNum);
        for (int i = titleNum + 1; i <= rows; i++) {
            // 从第2行开始取数据,默认第一行是表头.
            Row row = sheet.getRow(i);
            // 判断当前行是否是空行
            if (PoiUtil.isEmpty(row)) {
                continue;
            }
            // 创建实体类
            T entity = handlerBean.getClazz().newInstance();
            for (Map.Entry<Integer, Object[]> entry : fieldsMap.entrySet()) {
                int column = entry.getKey();
                // 从map中得到对应列的field.
                Field field = (Field) entry.getValue()[0];
                Excel attr = (Excel) entry.getValue()[1];
                // excel中取得值，并转换成对应类型
                Object val = PoiUtil.getCellValue(row, column);
                val = convertValue(field.getType(), attr.dateFormat(), val);
                // 字段名称
                String propertyName = field.getName();
                if (StrUtil.isNotEmpty(attr.targetAttr())) {
                    propertyName = field.getName() + "." + attr.targetAttr();
                } else {
                    val = convertSpecialValue(pictures, row.getRowNum(), column, attr, val);
                }
                invokeSetter(entity, propertyName, val);
            }
            list.add(entity);
        }
        return list;
    }

    /**
     * 创建一个map用于存放excel列的序号和field
     * @param sheet sheet
     * @param handlerBean excel处理对象
     * @param titleNum 标题所占行数
     * @return map
     */
    private static <T> Map<Integer, Object[]> createCellMap(Sheet sheet, ExcelBean<T> handlerBean, int titleNum) {
        Map<String, Integer> cellMap = new HashMap<>();
        // 获取表头
        Row heard = sheet.getRow(titleNum);
        for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++) {
            Cell cell = heard.getCell(i);
            if (cell != null) {
                String value = PoiUtil.getCellValue(heard, i).toString();
                cellMap.put(value, i);
            } else {
                cellMap.put(null, i);
            }
        }
        // 有数据时才处理 得到类的所有field.
        List<Object[]> fields = handlerBean.getFields();
        Map<Integer, Object[]> fieldsMap = new HashMap<>();
        for (Object[] objects : fields) {
            Excel attr = (Excel) objects[1];
            Integer column = cellMap.get(attr.name());
            if (column != null) {
                fieldsMap.put(column, objects);
            }
        }
        return fieldsMap;
    }

    /**
     * 根据字段类型获，转换值
     * @param fieldType 字段类型
     * @param dateFormat 日期格式
     * @param val 值
     * @return 转化后的值
     */
    private static Object convertValue(Class<?> fieldType, String dateFormat, Object val) {
        if (val != null && fieldType.isEnum()) {
            // 枚举字段特殊处理，显示信息->枚举
            if (!BaseEnum.class.isAssignableFrom(fieldType)) {
                throw new ServiceException(fieldType.getName() + " 必须是BaseEnum的子类");
            }
            val = Convert.toStr(val);
            // 根据显示名匹配对应地枚举
            Field[] enumFields = ReflectUtil.getFields(fieldType);
            for (Field enumField : enumFields) {
                if (!enumField.getType().isEnum()) {
                    continue;
                }
                BaseEnum baseEnum = (BaseEnum)ReflectUtil.getStaticFieldValue(enumField);
                if (baseEnum.getLabel().equals(val)) {
                    return baseEnum;
                }
            }
            // 未找到对应地枚举值，则报错
            throw new ServiceException(fieldType.getName() + " 值不正确");
        } else if (String.class == fieldType) {
            String str = Convert.toStr(val);
            if (NumberUtil.isNumber(str) && StrUtil.endWith(str, ".0")) {
                // 若是数字，且结尾是 .0 结尾，则取出前面整数部分
                val = StrUtil.subBefore(str, ".0", false);
            } else {
                if (StrUtil.isNotEmpty(dateFormat)) {
                    val = ExcelFieldHandler.format(dateFormat, val);
                } else {
                    val = Convert.toStr(val);
                }
            }
        } else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && NumberUtil.isNumber(Convert.toStr(val))) {
            val = Convert.toInt(val);
        } else if ((Long.TYPE == fieldType || Long.class == fieldType) && NumberUtil.isNumber(Convert.toStr(val))) {
            val = Convert.toLong(val);
        } else if (Double.TYPE == fieldType || Double.class == fieldType) {
            val = Convert.toDouble(val);
        } else if (Float.TYPE == fieldType || Float.class == fieldType) {
            val = Convert.toFloat(val);
        } else if (BigDecimal.class == fieldType) {
            val = Convert.toBigDecimal(val);
        } else if (Date.class == fieldType) {
            if (val instanceof String) {
                val = ExcelFieldHandler.parseDate(val);
            } else if (val instanceof Double) {
                val = DateUtil.getJavaDate((Double) val);
            }
        } else if (Boolean.TYPE == fieldType || Boolean.class == fieldType) {
            val = Convert.toBool(val, false);
        }
        return val;
    }

    /**
     * 转化特殊的字段值
     * @param pictures 图片Map
     * @param rowNum 当前行号
     * @param column 当前列号
     * @param attr 注解
     * @param val 值
     * @return 转化后的值
     */
    private static Object convertSpecialValue(Map<String, PictureData> pictures,
                                              int rowNum,
                                              int column,
                                              Excel attr,
                                              Object val) {
        // 存在子类对象的属性，则不处理
        if (StrUtil.isNotBlank(attr.targetAttr())) {
            return null;
        }

        if (StrUtil.isNotEmpty(attr.readConverterExp())) {
            val = ExcelFieldHandler.reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator());
        } else if (!attr.formatter().equals(ExcelFormatter.class)) {
            val = ExcelFieldHandler.customFormat(val, attr.formatter(), attr.args());
        } else if (ColumnType.IMAGE == attr.cellType() && CollectionUtil.isNotEmpty(pictures)) {
            PictureData image = pictures.get(rowNum + "_" + column);
            if (image == null) {
                val = "";
            } else {
                // todo: 暂未实现将图片上传，后续准备使用云存储将图片上传到云存储后，将链接保存到阿里云
                log.error("暂未实图片导入功能");
                throw new ServiceException("暂未实图片导入功能");
            }
        }
        return val;
    }

    /**
     * 调用Setter方法, 仅匹配方法名。
     * 支持多级，如：对象名.对象名.方法
     */
    private static <E> void invokeSetter(Object obj, String propertyName, E value) {
        Object object = obj;
        String[] names = StrUtil.split(propertyName, ".");
        for (int i = 0; i < names.length; i++) {
            if (i < names.length - 1) {
                String getter = ExcelFieldHandler.GETTER_PREFIX + StrUtil.upperFirst(names[i]);
                object = invokeMethod(object, getter, new Class[]{}, new Object[]{});
            } else {
                String setter = ExcelFieldHandler.SETTER_PREFIX + StrUtil.upperFirst(names[i]);
                invokeMethodByName(object, setter, new Object[]{value});
            }
        }
    }

    /**
     * 直接调用对象方法, 无视private/protected修饰符.
     * 用于一次性调用的情况，否则应使用getAccessibleMethod()函数获得Method后反复调用.
     * 同时匹配方法名+参数类型，
     */
    private static Object invokeMethod(Object obj, String methodName, Class<?>[] parameterTypes, Object[] args) {
        if (obj == null || methodName == null) {
            return null;
        }
        Method method = getAccessibleMethod(obj, methodName, parameterTypes);
        if (method == null) {
            log.debug("在 [{}] 中，没有找到 [{}] 方法 ", obj.getClass(), methodName);
            return null;
        }
        try {
            return method.invoke(obj, args);
        } catch (Exception e) {
            String errMsg = String.format("method: %s, obj: %s, args: %s", method, obj, Arrays.toString(args));
            throw new ServiceException(errMsg, e);
        }
    }

    /**
     * 根据名称和参数类型列表查找method
     *
     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
     * 如向上转型到Object仍无法找到, 返回null.
     * 匹配函数名+参数类型。
     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
     */
    private static Method getAccessibleMethod(Object obj, String methodName, Class<?>... parameterTypes) {
        // 为空不报错。直接返回 null
        if (obj == null) {
            return null;
        }
        Assert.notEmpty(methodName, "methodName can't be blank");
        for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
            try {
                Method method = searchType.getDeclaredMethod(methodName, parameterTypes);
                makeAccessible(method);
                return method;
            } catch (NoSuchMethodException e) {
                // 异常时，什么都不需要执行
            }
        }
        return null;
    }

    /**
     * 直接调用对象方法, 无视private/protected修饰符，
     * 用于一次性调用的情况，否则应使用getAccessibleMethodByName()函数获得Method后反复调用.
     * 只匹配函数名，如果有多个同名函数调用第一个。
     */
    private static Object invokeMethodByName(Object obj, String methodName, Object[] args) {
        Method method = getAccessibleMethodByName(obj, methodName, args.length);
        if (method == null) {
            // 如果为空不报错，直接返回空。
            log.debug("在 [{}] 中，没有找到 [{}] 方法 ", obj.getClass(), methodName);
            return null;
        }
        try {
            // 类型转换（将参数数据类型转换为目标方法参数类型）
            Class<?>[] cs = method.getParameterTypes();
            for (int i = 0; i < cs.length; i++) {
                if (args[i] != null && !args[i].getClass().equals(cs[i])) {
                    args[i] = convertValue(cs[i], null, args[i]);
                }
            }
            return method.invoke(obj, args);
        } catch (Exception e) {
            String errMsg = String.format("method: %s, obj: %s, args: %s", method, obj, Arrays.toString(args));
            throw new ServiceException(errMsg, e);
        }
    }

    /**
     * 更具名称和参数数量查找Method。
     *
     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
     * 如向上转型到Object仍无法找到, 返回null.
     * 只匹配函数名。
     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
     */
    private static Method getAccessibleMethodByName(Object obj, String methodName, int argsNum) {
        // 为空不报错。直接返回 null
        if (obj == null) {
            return null;
        }
        Assert.notEmpty(methodName, "methodName can't be blank");
        for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
            Method[] methods = searchType.getDeclaredMethods();
            for (Method method : methods) {
                if (method.getName().equals(methodName)
                        && method.getParameterTypes().length == argsNum) {
                    makeAccessible(method);
                    return method;
                }
            }
        }
        return null;
    }

    /**
     * 改变private/protected的方法为public，尽量不调用实际改动的语句，避免JDK的SecurityManager抱怨。
     */
    private static void makeAccessible(Method method) {
        boolean isNotPublic = !Modifier.isPublic(method.getModifiers())
                || !Modifier.isPublic(method.getDeclaringClass().getModifiers());
        if (isNotPublic && !method.isAccessible()) {
            method.setAccessible(true);
        }
    }
}